package spe.ui.treeprovider;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
//import java.util.HashMap;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import spe.model.notation.SPEMDiagram;
import spe.model.notation.SPEMDiagramImpl;
import spe.model.spem.MethodLibrary;
import spe.model.spem.MethodLibraryImpl;
import spe.model.spem.MethodPlugin;
import spe.model.spem.MethodPluginImpl;
import spe.model.spem.NodeElement;
import spe.model.spem.PackageElement;
import spe.model.spem.SPEMElement;
import spe.model.spem.SPERootElement;
import spe.ui.view.SPENavigatorRoot;


/*	@author: Dao Anh Vu
 * 	@last update: Feb 5, 2013 
 * 	@description: This is content provider for SPE project navigator
 * */
public class SPEMItemContentProvider implements ITreeContentProvider, IResourceChangeListener, PropertyChangeListener
{
	private static final Object[]   NO_CHILDREN = {};
    //private Map<String, Object> _wrapperCache = new HashMap<String, Object>();
	private Object[] _wrapperCache = null;
    private Viewer _viewer;
    //private SPEMFactory factory = new SPEMFactoryImpl("spe.ui.editor.SPEMGraphEditor");
    
    //Because we cannot refresh the tree viewer directly from another thread (to display thread),
    //so this runnable object will be invoked asynchronously whenever we want to update the content. 
    private Runnable _refreshViewer = new Runnable(){
    	@Override
    	public void run(){
    		Control ctr = _viewer.getControl();
    		if(ctr == null || ctr.isDisposed())
    			return;
    		
    		TreeViewer viewer = (TreeViewer) _viewer;
            TreePath[] treePaths = viewer.getExpandedTreePaths();
            viewer.refresh();
            viewer.setExpandedTreePaths(treePaths);
    	}
    };
    
    public SPEMItemContentProvider()
    {
    	ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }
    
	@Override
	public void dispose() 
	{
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
	}

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) 
	{
		//System.out.println(String.format("[SPEMItemContentProvider] old: %s; new: %s ", oldInput, newInput));
		_viewer = viewer;
	}

	@Override
	public Object[] getElements(Object inputElement) 
	{
		return getChildren(inputElement);
	}

	@Override
	public Object[] getChildren(Object parentElement) 
	{
		//System.out.println(String.format("[SPEMItemContentProvider] %s", parentElement.getClass()));
		
        if (IWorkspaceRoot.class.isInstance(parentElement)) 
        	return ((IWorkspaceRoot)parentElement).getProjects();
        
        if(SPENavigatorRoot.class.isInstance(parentElement))
        {
        	IProject [] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
        	return projects;
        }
        
        if(IProject.class.isInstance(parentElement))
			try {
				return ((IProject)parentElement).members();
			} catch (CoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        
        if(IFolder.class.isInstance(parentElement))
        {
        	//getModelChildren((IFile)parentElement);
        	for(Object obj: _wrapperCache)
        		System.out.println(String.format("Folder children %s", obj));
        	
        	return _wrapperCache;
        }
        
        if(SPERootElement.class.isInstance(parentElement))
        {
        	SPERootElement re = (SPERootElement)parentElement;
        	return re.getElements();
        }
        
        if(SPEMDiagram.class.isInstance(parentElement))
        {
        	return _wrapperCache;
        }
        
        if(PackageElement.class.isInstance(parentElement))
        {
        	List<SPEMElement> children = ((PackageElement) parentElement).getChildElements();
            return children.toArray(new SPEMElement[children.size()]);
        }

        return NO_CHILDREN;
	}

	@Override
	public Object getParent(Object element)
	{       
        if (IProject.class.isInstance(element)) 
        	return ((IProject)element).getWorkspace().getRoot();
        
        if (SPEMElement.class.isInstance(element)) 
        	return ((SPEMElement)element).getParent();
        
        return null;
	}

	@Override
	public boolean hasChildren(Object element) 
	{
        if (IWorkspaceRoot.class.isInstance(element)) 
        	return ((IWorkspaceRoot)element).getProjects().length > 0;
        	
        if (IProject.class.isInstance(element)) 
        {
        	IResource[] res;
        	if(!((IProject)element).isOpen())
        		return false;
        	
			try {
				res = ((IProject)element).members();
				for(int i=0; i<res.length; i++)
					if(IFolder.class.isInstance(res[i]))
						return true;
			} catch (CoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
        
        if(IFolder.class.isInstance(element))
        {
        	try {
        		//System.out.println(String.format("Folder: %s ", element));
				_wrapperCache = getFolderChildren((IFolder)element);
				return _wrapperCache.length > 0;
			} catch (CoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        	
        }
        
        if(SPERootElement.class.isInstance(element))
        {
        	SPERootElement re = (SPERootElement)element;
        	return re.getElements().length>0;
        }
        
        if(SPEMDiagram.class.isInstance(element))
        {
        	SPEMDiagram diagram = (SPEMDiagram)element;
        	List<NodeElement> nodes = diagram.getElements();
        	_wrapperCache = nodes.toArray(new NodeElement[nodes.size()]);
        	return _wrapperCache.length > 0;
        }
        
        if (MethodLibrary.class.isInstance(element)) 
            return ((MethodLibrary)element).getChildElements().size()>0;
            
        if(PackageElement.class.isInstance(element))
        	return ((PackageElement)element).getChildElements().size()>0;
        
        return false;
	}

	@Override
	public void resourceChanged(IResourceChangeEvent iea) 
	{
		Control ctr = _viewer.getControl();
		if(ctr==null || ctr.isDisposed())
			return;
		
		//Check if the current thread is the display thread
		Display d = _viewer.getControl().getDisplay();
		if(d.getThread() == Thread.currentThread())
			_refreshViewer.run();
		else{
			//if not, will invoke refresh action asynchronously
			d.asyncExec(_refreshViewer);
		}
	}

	@Override
	public void propertyChange(PropertyChangeEvent ev)
	{
		Control ctr = _viewer.getControl();
		if(ctr==null || ctr.isDisposed())
			return;
		
		Display d = _viewer.getControl().getDisplay();
		if(d.getThread() == Thread.currentThread())
			_refreshViewer.run();
		else{
			d.asyncExec(_refreshViewer);
		}
	}
	
	private Object[] getFolderChildren(IFolder folder) throws CoreException
	{
		List<Object> children = new ArrayList<Object>();
		IResource[] res = folder.members();
		SPERootElement rootElement;
		int i;
		
		for(i=0;i<res.length;i++)
		{
			//System.out.println(String.format("Folder: %s has child %s", folder.getName(), res[i].getName()));
			
			if(IFile.class.isInstance(res[i]) && res[i].getName().equals("spe-model.xml"))
			{
				rootElement = getModelChildren((IFile)res[i]);
				children.add(rootElement);
			}
			else
			{
				//System.out.println(String.format("Folder: %s has child %s", folder.getName(), res[i].getName()));
				children.add(res[i]);
			}
		}
		
		return children.toArray();
	}
	
	private SPERootElement getModelChildren(IFile file)
	{
		DocumentBuilder builder;
		Document doc = null;
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		SPERootElement rootElement;
		
		try {
			builder = documentBuilderFactory.newDocumentBuilder();
			doc = builder.parse(file.getContents());
			rootElement = getSPERootElement(doc);
			rootElement.setFile(file);
			return rootElement;
			
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	
	private SPERootElement getSPERootElement(Document doc)
	{
		SPERootElement rootElement = null;
		
		if(doc != null)
		{
			Element root = doc.getDocumentElement();
			if( !("spe-root".equals(root.getNodeName())) )
				return null;
			
			String name = root.getAttribute("name");
			String id = root.getAttribute("id");
			
			rootElement = getSPERootElement(root);
			rootElement.setId(id);
			rootElement.setName(name);
		}
		
		return rootElement;
	}
	
	private List<TempElement> temp_members = new ArrayList<TempElement>();
	
	private SPERootElement getSPERootElement(Element root)
	{
		NodeList children = root.getChildNodes();
		String elementName;
		int i = 0;
		SPERootElement re = new SPERootElement();
		SPEMElement speElement = null;
		
		temp_members.clear();
		
		for(i=0; i<children.getLength(); i++)
		{
			Node node = children.item(i);
			if(node.getNodeType() == Node.ELEMENT_NODE)
			{
				Element elem = (Element)node;
				elementName = elem.getNodeName();
				
				String id = elem.getAttribute("id");
				String name = elem.getAttribute("name");
				
				if(elementName.equals("diagram"))
				{
					//System.out.println(String.format("SPEMItemContentProvider %s", elementName));
					//SPEMDiagram diagram = (SPEMDiagram) factory.createElementById("spe.model.diagram.SPEMDiagram");
					speElement = new SPEMDiagramImpl();
					speElement.setId(id);
					if(name != null)
						speElement.setName(name);
					checkConsistExistedElement(re, speElement, node);
					re.addChildElement(speElement);
					checkIsMember(re, speElement, node);
					speElement.setParent(re);
				}
				else if(elementName.equals("method-library"))
				{
					//MethodLibrary ml = (MethodLibrary) factory.createElementById("spe.model.spem.method.plugin.MethodLibrary");
					speElement = new MethodLibraryImpl();
					speElement.setId(id);
					checkConsistExistedElement(re, speElement, node);
					re.addChildElement(speElement);
					checkIsMember(re, speElement, node);
					if(name != null)
						speElement.setName(name);
				}
				else if(elementName.equals("method-plugin"))
				{
					speElement = new MethodPluginImpl();
					speElement.setId(id);
					speElement.setName(name);
					checkConsistExistedElement(re, speElement, node);
					re.addChildElement(speElement);
					checkIsMember(re, speElement, node);
				}
				else if(elementName.equals("method-configuration"))
				{
					
				}
				else if(elementName.equals("activity"))
				{
				
				}
				else if(elementName.equals("work-definition"))
				{
				
				}
				else if(elementName.equals("planning-data"))
				{
					
				}
			}
		}
		return re;
	}
	
	/*	Check if any existed element that is a children of new element
	 *  Chua xong
	 * */
	private void checkConsistExistedElement(SPERootElement rootElement, SPEMElement newElement, Node node){
		SPEMElement[] children = rootElement.getElements();
		NodeList children_list = node.getChildNodes();
		if(children_list.getLength() <= 0)
			return;
		
		if( !PackageElement.class.isInstance(newElement))
			return;
		
		//VET CAN !!!!!!!!!!!
		for(SPEMElement e: children)
		{
			String eId = e.getId();
			String nodeId;
			for(int i=0; i<children_list.getLength(); i++){
				Node memberNode = children_list.item(i);
				if( (memberNode.getNodeType() == Node.ELEMENT_NODE) &&
						memberNode.getNodeName().equals("member")){
					Element elem = (Element)memberNode;
					nodeId = elem.getAttribute("id");
					if(nodeId.equals(eId)){
						((PackageElement)newElement).addChildElement(e);
					}
				}
			}
		}
	}
	
	private void checkIsMember(SPERootElement rootElement, SPEMElement newElement, Node node){
		List<String> strWillBeRemoved = new ArrayList<String>();
		List<TempElement> eWillBeRemoved = new ArrayList<TempElement>();
		for(TempElement te: temp_members){
			strWillBeRemoved.clear();
			for(String strMember: te.memberIds){
				if(strMember.equals(newElement.getId())){	
					((PackageElement)(te.element)).addChildElement(newElement);
					//te.memberIds.remove(newElement.getId());
					strWillBeRemoved.add(strMember);
				}
			}
			te.memberIds.removeAll(strWillBeRemoved);
			if(te.memberIds.isEmpty())
				eWillBeRemoved.add(te);
		}
		temp_members.removeAll(eWillBeRemoved);
		
		//add newElement
		TempElement te = null;
		NodeList children = node.getChildNodes();
		if(children.getLength() <= 0)
			return;
		
		te = new TempElement();
		te.element = newElement;
		for(int i=0; i<children.getLength(); i++){
			Node memberNode = children.item(i);
			if( (memberNode.getNodeType() == Node.ELEMENT_NODE) &&
					memberNode.getNodeName().equals("member")){
				Element elem = (Element)memberNode;
				te.memberIds.add(elem.getAttribute("id"));
			}
		}
		if(!te.memberIds.isEmpty())
			temp_members.add(te);
	}
	
	
	class TempElement{
		SPEMElement element;
		List<String> memberIds = new ArrayList<String>();
	}
}
